#region File Description
//-----------------------------------------------------------------------------
// Tank.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using BehaviorTrees;
using System;
using Microsoft.Xna.Framework.Audio;
using System.Collections.Generic;
#endregion

namespace SimpleAnimation
{
    /// <summary>
    /// Helper class for drawing a tank model with animated wheels and turret.
    /// </summary>
    public class Tank : Entity
    {
        #region Fields

        // The XNA framework Model object that we are going to display.
        Model tankModel;
		// behavior
		Node<Tank> behavior;
		/// <summary>
		/// Tank's behavior
		/// </summary>
		public Node<Tank> Behavior
		{
			get { return behavior; }
			set 
			{ 
				behavior = value;
				task = behavior.Evaluate(this);
				// new task resets finished value
				finished = false;
			}
		}
		Task<Tank> task;
        // Shortcut references to the bones that we are going to animate.
        // We could just look these up inside the Draw method, but it is more
        // efficient to do the lookups while loading and cache the results.
        ModelBone leftBackWheelBone;
        ModelBone rightBackWheelBone;
        ModelBone leftFrontWheelBone;
        ModelBone rightFrontWheelBone;
        ModelBone leftSteerBone;
        ModelBone rightSteerBone;
        ModelBone turretBone;
        ModelBone cannonBone;
        ModelBone hatchBone;


        // Store the original transform matrix for each animating bone.
        Matrix leftBackWheelTransform;
        Matrix rightBackWheelTransform;
        Matrix leftFrontWheelTransform;
        Matrix rightFrontWheelTransform;
        Matrix leftSteerTransform;
        Matrix rightSteerTransform;
        Matrix turretTransform;
        Matrix cannonTransform;
        Matrix hatchTransform;

        
        // Array holding all the bone transform matrices for the entire model.
        // We could just allocate this locally inside the Draw method, but it
        // is more efficient to reuse a single array, as this avoids creating
        // unnecessary garbage.
        Matrix[] boneTransforms;


        // Current animation positions.
        float wheelRotationValue;
        float steerRotationValue;
        float turretRotationValue;
        float cannonRotationValue;
        float hatchRotationValue;

		// other tanks
		List<Tank> enemies = new List<Tank>();

        /// <summary>
        /// Gets or sets the wheel rotation amount.
        /// </summary>
        public float WheelRotation
        {
            get { return wheelRotationValue; }
            set { wheelRotationValue = value; }
        }


        /// <summary>
        /// Gets or sets the steering rotation amount.
        /// </summary>
        public float SteerRotation
        {
            get { return steerRotationValue; }
            set { steerRotationValue = value; }
        }


        /// <summary>
        /// Gets or sets the turret rotation amount.
        /// </summary>
        public float TurretRotation
        {
            get { return turretRotationValue; }
            set { turretRotationValue = value; }
        }


        /// <summary>
        /// Gets or sets the cannon rotation amount.
        /// </summary>
        public float CannonRotation
        {
            get { return cannonRotationValue; }
            set { cannonRotationValue = value; }
        }


        /// <summary>
        /// Gets or sets the entry hatch rotation amount.
        /// </summary>
        public float HatchRotation
        {
            get { return hatchRotationValue; }
            set { hatchRotationValue = value; }
        }

        // additional fields and properties

        float velocity = 1000f;
		public float Velocity
		{
			get { return velocity; }
			set { velocity = value; }
		}
        Vector3 position = new Vector3(0f, 0f, 0f);
        public Vector3 Position
        {
			get { return position; }
			set 
			{
				if (FOV == null)
					FOV = new BoundingFrustum(CreateFOVMatrix());
				else
					FOV.Matrix = CreateFOVMatrix();
				sphere.Center = position = value; 
			}
        }
		Vector3 direction;
		public Vector3 Direction
		{
			get { return direction; }
			set
			{
				direction = value;
				//Angle = (float)Math.Acos(Vector3.Dot(Vector3.Backward, direction));
				if (FOV == null)
					FOV = new BoundingFrustum(CreateFOVMatrix());
				else
				    FOV.Matrix = CreateFOVMatrix();
			}
		}
        /// <summary>
        /// where to move
        /// </summary>
        public Vector3 MoveTarget
        {
            get;
            set;
        }
        /// <summary>
        /// where to shoot
        /// </summary>
        public Vector3 Target
        {
            get;
            set;
        }

		public float MinRange = 100f;
		public float MaxRange = 5000f;

		// indicates shot
		public bool bam = false;
		/// <summary>
		/// Area that is visible from the tank.
		/// </summary>
		public BoundingFrustum FOV
		{
			get;
			set;
		}
		BoundingSphere sphere;
		/// <summary>
		/// Sphere around tank.
		/// </summary>
		public BoundingSphere BSphere
		{
			get { return sphere; }
			set { sphere = value; }
		}
		/// <summary>
		/// Active enemy
		/// </summary>
		public Tank Enemy
		{
			get;
			set;
		}

		public int HitPoints
		{
			get;
			set;
		}
		int maxHitPoints;
		public int MaxHitPoints
		{
			get { return maxHitPoints; }
			set
			{
				HitPoints = maxHitPoints = value;
			}
		}
		// determines whether attack sequence was initiated
		public bool IsAttacking
		{
			get;
			set;
		}
        #endregion

		public Tank()
		{
			Direction = new Vector3(0f, 0f, 1f);
			sphere.Radius = 400f;//?
			FOV = new BoundingFrustum(CreateFOVMatrix());
			MaxHitPoints = 1000;
			velocity = 1000f;
		}

		public Matrix CreateFOVMatrix()
		{
			return Matrix.CreateLookAt(Position, Direction, Vector3.Up)
				* Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver2, 1f, 1f, MaxRange);
		}
        /// <summary>
        /// Loads the tank model.
        /// </summary>
        public void Load(ContentManager content)
        {
            // Load the tank model from the ContentManager.
            tankModel = content.Load<Model>("tank");
			
            // Look up shortcut references to the bones we are going to animate.
            leftBackWheelBone = tankModel.Bones["l_back_wheel_geo"];
            rightBackWheelBone = tankModel.Bones["r_back_wheel_geo"];
            leftFrontWheelBone = tankModel.Bones["l_front_wheel_geo"];
            rightFrontWheelBone = tankModel.Bones["r_front_wheel_geo"];
            leftSteerBone = tankModel.Bones["l_steer_geo"];
            rightSteerBone = tankModel.Bones["r_steer_geo"];
            turretBone = tankModel.Bones["turret_geo"];
            cannonBone = tankModel.Bones["canon_geo"];
            hatchBone = tankModel.Bones["hatch_geo"];

            // Store the original transform matrix for each animating bone.
            leftBackWheelTransform = leftBackWheelBone.Transform;
            rightBackWheelTransform = rightBackWheelBone.Transform;
            leftFrontWheelTransform = leftFrontWheelBone.Transform;
            rightFrontWheelTransform = rightFrontWheelBone.Transform;
            leftSteerTransform = leftSteerBone.Transform;
            rightSteerTransform = rightSteerBone.Transform;
            turretTransform = turretBone.Transform;
            cannonTransform = cannonBone.Transform;
            hatchTransform = hatchBone.Transform;

            // Allocate the transform matrix array.
            boneTransforms = new Matrix[tankModel.Bones.Count];
        }

        /// <summary>
        /// Draws the tank model, using the current animation settings.
        /// </summary>
        public void Draw(Matrix world, Matrix view, Matrix projection)
        {
            // Set the world matrix as the root transform of the model.
            tankModel.Root.Transform = world;

            // Calculate matrices based on the current animation position.
            Matrix wheelRotation = Matrix.CreateRotationX(wheelRotationValue);
            Matrix steerRotation = Matrix.CreateRotationY(steerRotationValue);
            Matrix turretRotation = Matrix.CreateRotationY(turretRotationValue);
            Matrix cannonRotation = Matrix.CreateRotationX(cannonRotationValue);
            Matrix hatchRotation = Matrix.CreateRotationX(hatchRotationValue);

            // Apply matrices to the relevant bones.
            leftBackWheelBone.Transform = wheelRotation * leftBackWheelTransform;
            rightBackWheelBone.Transform = wheelRotation * rightBackWheelTransform;
            leftFrontWheelBone.Transform = wheelRotation * leftFrontWheelTransform;
            rightFrontWheelBone.Transform = wheelRotation * rightFrontWheelTransform;
            leftSteerBone.Transform = steerRotation * leftSteerTransform;
            rightSteerBone.Transform = steerRotation * rightSteerTransform;
			// nice effect
			if (HitPoints <= 0)
				turretBone.Transform = Matrix.CreateScale(0.1f);
			else
				turretBone.Transform = turretRotation * turretTransform;
            cannonBone.Transform = cannonRotation * cannonTransform;
            hatchBone.Transform = hatchRotation * hatchTransform;

            // Look up combined bone matrices for the entire model.
            tankModel.CopyAbsoluteBoneTransformsTo(boneTransforms);

            // Draw the model.
            foreach (ModelMesh mesh in tankModel.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {			
                    effect.World = boneTransforms[mesh.ParentBone.Index];
                    effect.View = view;
                    effect.Projection = projection;

                    effect.EnableDefaultLighting();
					float dmg = (((float)HitPoints <=0) ? 0 : (float)HitPoints) / (float)maxHitPoints;
					effect.AmbientLightColor = new Vector3((1-(float)Math.Pow((float)dmg,0.7)), 0f, 0f);
                }

                mesh.Draw();
            }
        }

		/// <summary>
		/// Adds enemy tank to the search list.
		/// </summary>
		/// <param name="tank"></param>
		public void AddEnemy(Tank tank)
		{
			enemies.Add(tank);
		}
		/// <summary>
		/// Updates logic.
		/// </summary>
		bool finished = false;
		internal void Update(GameTime gameTime)
		{
			if (HitPoints <= 0)
				return; // dead
			// this should be solved by providing another behavior or by repeating
			if (finished)
				return;
			if (task != null)
			{
				TaskResult result = task.Execute(this, gameTime);
				if (result == TaskResult.Success)
					finished = true;
				if (result == TaskResult.Failure)
					// try again
					task = behavior.Evaluate(this);
			}
		}

        // additional methods for actions

		/// <summary>
		/// Checks if there is another tank in the field of view.
		/// If there is, then sets is as a target(via Enemy property) and returns success.
		/// </summary>
		/// <param name="tank"></param>
		/// <param name="gameTime"></param>
		/// <returns></returns>
		public static TaskResult FindEnemy(Tank tank, GameTime gameTime)
		{
			foreach (Tank enemy in tank.enemies)
			{
				//if (tank.FOV.Contains(enemy.BSphere) != ContainmentType.Disjoint)
				//{
				//    if (enemy.HitPoints > 0)
				//    {
				//        tank.Enemy = enemy;
				//        return TaskResult.Success;
				//    }
				//}
				Vector3 toAim = Vector3.Transform(tank.Direction, Matrix.CreateRotationY(tank.TurretRotation));
				if(Math.Acos(Vector3.Dot(toAim, Vector3.Normalize(enemy.Position - tank.Position))) <= MathHelper.PiOver4)
				{
					 if (enemy.HitPoints > 0)
				    {
				        tank.Enemy = enemy;
				        return TaskResult.Success;
				    }
				}
			}
			return TaskResult.Running;
		}
		public static TaskResult EnemyDead(Tank tank, GameTime gameTime)
		{
			if (tank.Enemy.HitPoints <= 0)
				return TaskResult.Success;
			else
				return TaskResult.Failure;
		}

        public static TaskResult OpenHatch(Tank tank, GameTime gameTime)
        {
            float time = (float)gameTime.ElapsedGameTime.TotalSeconds;
            
            tank.HatchRotation -= time * 5f; //MathHelper.Clamp((float)Math.Sin(time * 2) * 2, -1, 0);
            if (tank.HatchRotation <= -1f)
            {
                tank.HatchRotation = -1f;
                return TaskResult.Success;
            }
            return TaskResult.Running;
        }
        public static TaskResult CloseHatch(Tank tank, GameTime gameTime)
        {
            float time = (float)gameTime.ElapsedGameTime.TotalSeconds;

            tank.HatchRotation += time * 15f;
            if (tank.HatchRotation >= 0f)
            {
                tank.HatchRotation = 0f;
                return TaskResult.Success;
            }
            return TaskResult.Running;
        }

        public static TaskResult RotateWheel(Tank tank, GameTime gameTime)
        {
            float time = (float)gameTime.TotalGameTime.TotalSeconds;

            tank.WheelRotation = time * 5;

            return TaskResult.Running;
        }

		// doesn't work
        public static TaskResult RotateSteer(Tank tank, GameTime gameTime)
        {
            float time = (float)gameTime.ElapsedGameTime.TotalSeconds;

			// calc angle between tank direction and vector to movetarget
			Vector3 toMoveTarget = Vector3.Normalize(tank.MoveTarget - tank.Position);
			float angle = (float)Math.Acos(Vector3.Dot(tank.Direction, toMoveTarget));
			// if negative then turn left, positive then right (or otherwise)
			float z = Vector3.Cross(tank.Direction, toMoveTarget).Z;

			// weird
			float steerAmount = angle / 50;
			steerAmount = (z > 0) ? -steerAmount : steerAmount;

			if (tank.SteerRotation > angle)
			{
				//tank.Direction = toMoveTarget;
				tank.SteerRotation -= steerAmount;
				tank.Direction = Vector3.Transform(tank.Direction, Matrix.CreateRotationY(-steerAmount));
				if (tank.SteerRotation < 0.05)
					return TaskResult.Success;
				else
					return TaskResult.Running;
			}
			else
			{
				tank.SteerRotation += steerAmount;
				tank.Direction = Vector3.Transform(tank.Direction, Matrix.CreateRotationY(steerAmount));
				return TaskResult.Running;
			}
            return TaskResult.Success;
        }

		/// <summary>
		/// Rotates turret towards the target. Stupidly.
		/// </summary>
		/// <param name="tank"></param>
		/// <param name="gameTime"></param>
		/// <returns></returns>
        public static TaskResult RotateTurret(Tank tank, GameTime gameTime)
        {
            float time = (float)gameTime.ElapsedGameTime.TotalSeconds;
            float dist = Vector3.Distance(tank.Target, tank.Position);
            Vector3 toTarget = tank.Target - tank.Position;
            toTarget.Y = 0f;
            Vector3 toAim = Vector3.Transform(tank.Direction, Matrix.CreateRotationY(tank.TurretRotation));
            toAim.Y = 0f;

			toTarget.Normalize();
			toAim.Normalize();

			Vector3 cr = Vector3.Cross(toAim, toTarget);
			float dir;
			if (cr.Y < 0)
				dir = -1f;
			else
				dir = 1f;

            if (Vector3.Distance(toTarget, toAim) < .01f)
                return TaskResult.Success;

            tank.TurretRotation += time * dir;
			// rebuild FOV frustum
			tank.FOV.Matrix = tank.CreateFOVMatrix();
            return TaskResult.Running;
        }
		/// <summary>
		/// Rotates canon basing on a distance
		/// </summary>
		public static TaskResult RotateCannon(Tank tank, GameTime gameTime)
		{
			float time = (float)gameTime.ElapsedGameTime.TotalSeconds;
			// some memory would be useful (either in tank entity or somewhere else?)
			float distance = Vector3.Distance(tank.Position, tank.Target);
			if ((distance > tank.MaxRange) || (distance < tank.MinRange))
				return TaskResult.Failure;
			// again, memory?
			// desired angle
			float a = distance / (tank.MaxRange) * MathHelper.PiOver2;

			// negative CannonRotation is up, positive is down
			if (Math.Abs(Math.Abs(tank.CannonRotation) - a) < 0.1f)
				return TaskResult.Success;

			if (Math.Abs(tank.CannonRotation) > a)
				tank.CannonRotation += time;
			else
				tank.CannonRotation -= time;

			return TaskResult.Running;
		}

		/// <summary>
		/// Rotates tank towards the move target. In place although it's tank on wheels...
		/// </summary>
		public enum RotationDirection
		{
			Unknown,
			Left, // left or right can be switched, it doesn't matter;p
			Right
		}
		public RotationDirection rotationDirection = RotationDirection.Unknown;

		public float Angle
		{
			get;
			set;
		}

		public static TaskResult RotateTank(Tank tank, GameTime gameTime)
		{
			float time = (float)gameTime.ElapsedGameTime.TotalSeconds;
			
			Vector3 toMoveTarget = tank.MoveTarget - tank.Position;
			toMoveTarget.Y = 0f;
			Vector3 direction = tank.Direction;
			direction.Y = 0f;
			//float angle = (float)Math.Acos(Vector3.Dot(toMoveTarget, tank.Direction));
			// example use of some kind of "memory"
			// which is inherent to the entity, so it has to be implemented by the entity/its actions
			if (tank.rotationDirection == RotationDirection.Unknown)
			{
				Vector3 cr = Vector3.Cross(toMoveTarget, direction);
				if (cr.Y < 0)
					tank.rotationDirection = RotationDirection.Left;
				else
					tank.rotationDirection = RotationDirection.Right;
			}

			toMoveTarget.Normalize();
			//direction.Normalize();

			if (Vector3.Distance(toMoveTarget, direction) < .01f)
			{
				tank.rotationDirection = RotationDirection.Unknown;
				return TaskResult.Success;
			}
			float angle = (tank.rotationDirection == RotationDirection.Left) ? time : -time;
			tank.Angle += angle;
			// backward is default tank direction
			tank.Direction = Vector3.Transform(Vector3.Backward, Matrix.CreateRotationY(tank.Angle));
			return TaskResult.Running;
		}

        public static TaskResult MoveTo(Tank tank, GameTime gameTime)
        {
            float d = (float)gameTime.ElapsedGameTime.TotalSeconds;

			// this '50' value is pretty buggy
            if (Vector3.Distance(tank.Position, tank.MoveTarget) < 50)
                return TaskResult.Success;
            else
            {
                tank.Position += tank.Direction * d * tank.velocity;
                return TaskResult.Running;
            }
        }

		/// <summary>
		/// Shoots. Weird method to do such thing but ok for now.
		/// </summary>
		public static TaskResult Bam(Tank tank, GameTime gameTime)
		{
			tank.bam = true;
			tank.IsAttacking = false;
			return TaskResult.Success;
		}
		/// <summary>
		/// Deals portion of damage to the active enemy
		/// </summary>
		public static TaskResult Damage(Tank tank, GameTime gameTime)
		{
			Random r = new Random();
			int dmg = r.Next() % 100 + 50;
			tank.Enemy.HitPoints -= dmg;
			return TaskResult.Success;
		}
	}

    // predefined behaviors

    /// <summary>
    /// Behavior which moves the tank to the desired position.
	/// 1) rotate tank
	/// 2) move the tank
    /// </summary>
    /// <typeparam name="E"></typeparam>
    public class MoveToBehavior<E> : Sequence<E> where E : Tank
    {
        Vector3 target;
        public MoveToBehavior(Vector3 target)
            : base()
        {
            this.target = target;
			
			Add(new BehaviorTrees.Action<E>(Tank.RotateTank));
			Add(new Parallel<E>(new BehaviorTrees.Action<E>(Tank.MoveTo))
			{
			    new BehaviorTrees.Action<E>(Tank.RotateWheel)
			});
        }
		public override Task<E> Evaluate(E entity)
		{
			entity.MoveTarget = target;
			return base.Evaluate(entity);
		}
    }
    /// <summary>
    /// First rotate turret, then cannon so that cannon aims at the target.
    /// </summary>
    /// <typeparam name="E"></typeparam>
    public class AcquireTargetBehavior<E> : Sequence<E> where E : Tank
    {
        Vector3 target;

        public AcquireTargetBehavior(Vector3 target)
            : base()
        {
            this.target = target;

            Add(new BehaviorTrees.Action<E>(Tank.RotateTurret));
            Add(new BehaviorTrees.Action<E>(Tank.RotateCannon));
        }

		public override Task<E> Evaluate(E entity)
        {
            // set the aim target
			entity.Target = target;
			return base.Evaluate(entity);
        }
    }
	/// <summary>
	/// Resets aim forward
	/// </summary>
	/// <typeparam name="E"></typeparam>
	public class ResetAimBehavior<E> : Sequence<E> where E : Tank
	{
        public ResetAimBehavior()
            : base()
        {
            Add(new BehaviorTrees.Action<E>(Tank.RotateTurret));
            //Add(new BehaviorTrees.Action<E>(Tank.RotateCannon));
        }

		public override Task<E> Evaluate(E entity)
        {
            // set the aim target
			entity.Target = entity.Position + entity.Direction;
			return base.Evaluate(entity);
        }
	}
	/// <summary>
	/// Target the enemy and shots
	/// </summary>
	/// <typeparam name="E"></typeparam>
	public class DestroyEnemyBehavior<E> : Sequence<E> where E : Tank
	{
		public DestroyEnemyBehavior()
            : base()
        {
            Add(new BehaviorTrees.Action<E>(Tank.RotateTurret));
            Add(new BehaviorTrees.Action<E>(Tank.RotateCannon));
			Add(new BehaviorTrees.Action<E>(Tank.Bam));
			Add(new BehaviorTrees.Action<E>(Tank.Damage));
        }

		public override Task<E> Evaluate(E entity)
		{
			entity.Target = entity.Enemy.Position;
			entity.IsAttacking = true;
			return base.Evaluate(entity);
		}
	}
	/// <summary>
	/// Moves the tank to specified patrol checkpoint and checks for the enemies in sight.
	/// If find an enemy, patrol succeeds
	/// </summary>
	/// <typeparam name="E"></typeparam>
	//public class PatrolBehavior<E> : Parallel<E> where E : Tank
	//{
	//    Vector3 checkpoint;

	//    public PatrolBehavior(Vector3 checkpoint)
	//        : base()
	//    {
	//        this.checkpoint = checkpoint;
	//        Add(new Watch<Tank>(Tank.CheckFOV, new Repeater<Tank>(
	//        Add(new BehaviorTrees.Action<E>(Tank.CheckFOV));
	//        Add(new MoveToBehavior<E>(checkpoint));
	//    }
	//}
}
